/*
 * Copyright (C) 2008 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect.testing.testers;

import static com.google.common.collect.testing.features.CollectionFeature.ALLOWS_NULL_VALUES;
import static com.google.common.collect.testing.features.CollectionFeature.SUPPORTS_REMOVE;
import static com.google.common.collect.testing.features.CollectionSize.ONE;
import static com.google.common.collect.testing.features.CollectionSize.ZERO;

import com.google.common.annotations.GwtCompatible;
import com.google.common.collect.testing.AbstractCollectionTester;
import com.google.common.collect.testing.MinimalCollection;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.junit.Ignore;

/**
 * A generic JUnit test which tests {@code retainAll} operations on a collection. Can't be invoked
 * directly; please see {@link com.google.common.collect.testing.CollectionTestSuiteBuilder}.
 *
 * @author Chris Povirk
 */
@SuppressWarnings("unchecked") // too many "unchecked generic array creations"
@GwtCompatible
@Ignore // Affects only Android test runner, which respects JUnit 4 annotations on JUnit 3 tests.
public class CollectionRetainAllTester<E> extends AbstractCollectionTester<E>
{

    /**
     * A collection of elements to retain, along with a description for use in failure messages.
     */
    private class Target
    {
        private final Collection<E> toRetain;
        private final String description;

        private Target(Collection<E> toRetain, String description)
        {
            this.toRetain = toRetain;
            this.description = description;
        }

        @Override
        public String toString()
        {
            return description;
        }
    }

    private Target empty;
    private Target disjoint;
    private Target superset;
    private Target nonEmptyProperSubset;
    private Target sameElements;
    private Target partialOverlap;
    private Target containsDuplicates;
    private Target nullSingleton;

    @Override
    public void setUp() throws Exception
    {
        super.setUp();

        empty = new Target(emptyCollection(), "empty");
        /*
         * We test that nullSingleton.retainAll(disjointList) does NOT throw a
         * NullPointerException when disjointList does not, so we can't use
         * MinimalCollection, which throws NullPointerException on calls to
         * contains(null).
         */
        List<E> disjointList = Arrays.asList(e3(), e4());
        disjoint = new Target(disjointList, "disjoint");
        superset = new Target(MinimalCollection.of(e0(), e1(), e2(), e3(), e4()), "superset");
        nonEmptyProperSubset = new Target(MinimalCollection.of(e1()), "subset");
        sameElements = new Target(Arrays.asList(createSamplesArray()), "sameElements");
        containsDuplicates =
                new Target(MinimalCollection.of(e0(), e0(), e3(), e3()), "containsDuplicates");
        partialOverlap = new Target(MinimalCollection.of(e2(), e3()), "partialOverlap");
        nullSingleton = new Target(Collections.<E>singleton(null), "nullSingleton");
    }

    // retainAll(empty)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(ZERO)
    public void testRetainAll_emptyPreviouslyEmpty()
    {
        expectReturnsFalse(empty);
        expectUnchanged();
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    @CollectionSize.Require(ZERO)
    public void testRetainAll_emptyPreviouslyEmptyUnsupported()
    {
        expectReturnsFalseOrThrows(empty);
        expectUnchanged();
    }

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = ZERO)
    public void testRetainAll_emptyPreviouslyNonEmpty()
    {
        expectReturnsTrue(empty);
        expectContents();
        expectMissing(e0(), e1(), e2());
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = ZERO)
    public void testRetainAll_emptyPreviouslyNonEmptyUnsupported()
    {
        expectThrows(empty);
        expectUnchanged();
    }

    // retainAll(disjoint)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(ZERO)
    public void testRetainAll_disjointPreviouslyEmpty()
    {
        expectReturnsFalse(disjoint);
        expectUnchanged();
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    @CollectionSize.Require(ZERO)
    public void testRetainAll_disjointPreviouslyEmptyUnsupported()
    {
        expectReturnsFalseOrThrows(disjoint);
        expectUnchanged();
    }

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = ZERO)
    public void testRetainAll_disjointPreviouslyNonEmpty()
    {
        expectReturnsTrue(disjoint);
        expectContents();
        expectMissing(e0(), e1(), e2());
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = ZERO)
    public void testRetainAll_disjointPreviouslyNonEmptyUnsupported()
    {
        expectThrows(disjoint);
        expectUnchanged();
    }

    // retainAll(superset)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    public void testRetainAll_superset()
    {
        expectReturnsFalse(superset);
        expectUnchanged();
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    public void testRetainAll_supersetUnsupported()
    {
        expectReturnsFalseOrThrows(superset);
        expectUnchanged();
    }

    // retainAll(subset)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testRetainAll_subset()
    {
        expectReturnsTrue(nonEmptyProperSubset);
        expectContents(nonEmptyProperSubset.toRetain);
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testRetainAll_subsetUnsupported()
    {
        expectThrows(nonEmptyProperSubset);
        expectUnchanged();
    }

    // retainAll(sameElements)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    public void testRetainAll_sameElements()
    {
        expectReturnsFalse(sameElements);
        expectUnchanged();
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    public void testRetainAll_sameElementsUnsupported()
    {
        expectReturnsFalseOrThrows(sameElements);
        expectUnchanged();
    }

    // retainAll(partialOverlap)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testRetainAll_partialOverlap()
    {
        expectReturnsTrue(partialOverlap);
        expectContents(e2());
    }

    @CollectionFeature.Require(absent = SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testRetainAll_partialOverlapUnsupported()
    {
        expectThrows(partialOverlap);
        expectUnchanged();
    }

    // retainAll(containsDuplicates)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(ONE)
    public void testRetainAll_containsDuplicatesSizeOne()
    {
        expectReturnsFalse(containsDuplicates);
        expectContents(e0());
    }

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testRetainAll_containsDuplicatesSizeSeveral()
    {
        expectReturnsTrue(containsDuplicates);
        expectContents(e0());
    }

    // retainAll(nullSingleton)

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(ZERO)
    public void testRetainAll_nullSingletonPreviouslyEmpty()
    {
        expectReturnsFalse(nullSingleton);
        expectUnchanged();
    }

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = ZERO)
    public void testRetainAll_nullSingletonPreviouslyNonEmpty()
    {
        expectReturnsTrue(nullSingleton);
        expectContents();
    }

    @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
    @CollectionSize.Require(ONE)
    public void testRetainAll_nullSingletonPreviouslySingletonWithNull()
    {
        initCollectionWithNullElement();
        expectReturnsFalse(nullSingleton);
        expectContents(createArrayWithNullElement());
    }

    @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
    @CollectionSize.Require(absent = {ZERO, ONE})
    public void testRetainAll_nullSingletonPreviouslySeveralWithNull()
    {
        initCollectionWithNullElement();
        expectReturnsTrue(nullSingleton);
        expectContents(nullSingleton.toRetain);
    }

    // nullSingleton.retainAll()

    @CollectionFeature.Require({SUPPORTS_REMOVE, ALLOWS_NULL_VALUES})
    @CollectionSize.Require(absent = ZERO)
    public void testRetainAll_containsNonNullWithNull()
    {
        initCollectionWithNullElement();
        expectReturnsTrue(disjoint);
        expectContents();
    }

    // retainAll(null)

    /*
     * AbstractCollection fails the retainAll(null) test when the subject
     * collection is empty, but we'd still like to test retainAll(null) when we
     * can. We split the test into empty and non-empty cases. This allows us to
     * suppress only the former.
     */

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(ZERO)
    public void testRetainAll_nullCollectionReferenceEmptySubject()
    {
        try
        {
            collection.retainAll(null);
            // Returning successfully is not ideal, but tolerated.
        }
        catch (NullPointerException tolerated)
        {
        }
    }

    @CollectionFeature.Require(SUPPORTS_REMOVE)
    @CollectionSize.Require(absent = ZERO)
    public void testRetainAll_nullCollectionReferenceNonEmptySubject()
    {
        try
        {
            collection.retainAll(null);
            fail("retainAll(null) should throw NullPointerException");
        }
        catch (NullPointerException expected)
        {
        }
    }

    private void expectReturnsTrue(Target target)
    {
        String message = Platform.format("retainAll(%s) should return true", target);
        assertTrue(message, collection.retainAll(target.toRetain));
    }

    private void expectReturnsFalse(Target target)
    {
        String message = Platform.format("retainAll(%s) should return false", target);
        assertFalse(message, collection.retainAll(target.toRetain));
    }

    private void expectThrows(Target target)
    {
        try
        {
            collection.retainAll(target.toRetain);
            String message = Platform.format("retainAll(%s) should throw", target);
            fail(message);
        }
        catch (UnsupportedOperationException expected)
        {
        }
    }

    private void expectReturnsFalseOrThrows(Target target)
    {
        String message = Platform.format("retainAll(%s) should return false or throw", target);
        try
        {
            assertFalse(message, collection.retainAll(target.toRetain));
        }
        catch (UnsupportedOperationException tolerated)
        {
        }
    }
}
